package com.self.console.service;


import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Pipeline;
import redis.clients.util.MurmurHash;

import javax.annotation.PostConstruct;
import java.util.*;

/**
 * Created by derek on 14-5-20.
 */
@Component
public class RedisDelegate {

    private final transient Logger logger = LoggerFactory.getLogger(RedisDelegate.class);
    private List<JedisPool> jedisPools;
    private HashMap<Integer, Integer> _map = Maps.newHashMap();
    private static final String PREFIX_REDIS = "redis-";
    private Set<String> prefixs;
    private MurmurHash murmurHash = new MurmurHash();

    // 使用redis服务时请确定在key中加上前缀，并且和其他前缀没有冲突
    @PostConstruct
    public void init() {
        set_map();
        prefixs = smembers(PREFIX_REDIS);
    }

    public void flushRedis() {
        logger.info("开始清理全局缓存");
        if (jedisPools == null || jedisPools.size() == 0) {
            logger.info("jedisPools为空 或者为0");
            return;
        }
        for (JedisPool tmp : jedisPools) {
            int retry = 3;
            while (retry > 0) {
                try {
                    tmp.getResource().flushDB();
                    break;
                } catch (Exception e) {
                    retry--;
                    logger.info("清理缓存失败 重试三次");
                }
            }
        }
        logger.info("全局缓存清理完成");
    }

    public List<JedisPool> getJedisPools() {
        return jedisPools;
    }

    public void setJedisPools(List<JedisPool> jedisPools) {
        this.jedisPools = jedisPools;
    }

    public Jedis getClient() {
        return jedisPools.get(0).getResource();
    }

    public Pipeline getPipeline() {
        return jedisPools.get(0).getResource().pipelined();
    }

    public void printPrefixs() {
        for (String prefix : prefixs) {
            logger.info(prefix);
        }
    }

    public void returnResource(Jedis client) {
        try {
            jedisPools.get(0).returnResource(client);
        } catch (Exception ex) {
            logger.error("redis 连接回收失败", ex);
        }
    }

    public synchronized boolean addPrefix(String prefix) {
        for (String s : prefixs) {
            if (isPrefix(prefix, s)) {
                System.out.println(s);
                return false;
            }
        }
        sadd(PREFIX_REDIS, prefix);
        return prefixs.add(prefix);
    }

    public Set<String> getKeysByPatten(final String pattern) {
        Jedis client = getClient();
        Set<String> ret = client.keys(pattern);
        return ret;
    }

    /*
     * 设置单个key-》value对 批量操作请尽量使用mset
     */
    public boolean set(String key, String value) {
        JedisPool jedisPool = getJedisPool(key);
        Jedis client = null;
        try {
            client = jedisPool.getResource();
            client.set(key, value);
            return true;
        } catch (Exception e) {
            if (client != null) {
                client.disconnect();
            }
            logger.error("key=" + key + " value=" + value);
            logger.error("getFromRedis is error", e);
            return false;
        } finally {
            if (client != null) {
                jedisPool.returnResource(client);
            }
        }
    }

    /*
     * 设置单个key-> value对 批量操作请尽量使用mset
     */
    public boolean setex(final String key, final int seconds, final String value) {
        JedisPool jedisPool = getJedisPool(key);
        Jedis client = null;
        try {
            client = jedisPool.getResource();
            client.setex(key, seconds, value);
            return true;
        } catch (Exception e) {
            if (client != null) {
                client.disconnect();
            }
            logger.error("key=" + key + " value=" + value);
            logger.error("getFromRedis is error", e);
            return false;
        } finally {
            if (client != null) {
                jedisPool.returnResource(client);
            }
        }
    }

    /*
     * 查看过期时间 Parameters: key Returns: Integer reply, returns the remaining time
     * to live in seconds of a key that has an EXPIRE. If the Key does not
     * exists or does not have an associated expire, -1 is returned.
     */
    public Long ttl(String key) {
        JedisPool jedisPool = getJedisPool(key);
        Jedis client = null;
        try {
            client = jedisPool.getResource();
            return client.ttl(key);
        } catch (Exception e) {
            if (client != null) {
                client.disconnect();
            }
            logger.error("key=" + key);
            logger.error("getFromRedis is error", e);
            return null;
        } finally {
            if (client != null) {
                jedisPool.returnResource(client);
            }
        }
    }

    /*
     * 获取单个key对应的value 批量操作请尽量使用mget
     */
    public String get(String key) {
        JedisPool jedisPool = getJedisPool(key);
        Jedis client = null;
        try {
            client = jedisPool.getResource();
            return client.get(key);
        } catch (Exception e) {
            if (client != null) {
                client.disconnect();
            }
            logger.error("key=" + key);
            logger.error("getFromRedis is error", e);
            return null;
        } finally {
            if (client != null) {
                jedisPool.returnResource(client);
            }
        }
    }

    /*
     * 自增1
     */
    public Long incr(final String key) {
        JedisPool jedisPool = getJedisPool(key);
        Jedis client = null;
        try {
            client = jedisPool.getResource();
            return client.incr(key);
        } catch (Exception e) {
            if (client != null) {
                client.disconnect();
            }
            logger.error("key=" + key);
            logger.error("incrToRedis is error", e);
            return null;
        } finally {
            if (client != null) {
                jedisPool.returnResource(client);
            }
        }
    }

    /*
     * 自增一个Integer
     */
    public Long incrBy(final String key, final long integer) {
        JedisPool jedisPool = getJedisPool(key);
        Jedis client = null;
        try {
            client = jedisPool.getResource();
            return client.incrBy(key, integer);
        } catch (Exception e) {
            if (client != null) {
                client.disconnect();
            }
            logger.error("key=" + key);
            logger.error("incrToRedis is error", e);
            return null;
        } finally {
            if (client != null) {
                jedisPool.returnResource(client);
            }
        }
    }

    public boolean mset(String[] keyvalues) {
        int length = keyvalues.length;
        if (length % 2 != 0) {
            return false;
        }
        Map<Integer, List<String>> multiKV = new HashMap<Integer, List<String>>();
        for (int i = 0; i < length; i += 2) {
            String key = keyvalues[i];
            String value = keyvalues[i + 1];
            int pos = getHostPos(key);
            if (multiKV.containsKey(pos)) {
                multiKV.get(pos).add(key);
                multiKV.get(pos).add(value);
            } else {
                List<String> kv = new ArrayList<String>();
                kv.add(key);
                kv.add(value);
                multiKV.put(pos, kv);
            }
        }

        for (Map.Entry<Integer, List<String>> entry : multiKV.entrySet()) {
            int pos = entry.getKey();
            Jedis client = null;
            try {
                client = jedisPools.get(pos).getResource();
                List<String> ls = entry.getValue();
                client.mset((String[]) ls.toArray(new String[ls.size()]));
            } catch (Exception e) {
                if (client != null) {
                    client.disconnect();
                }
                logger.error("Redis is error", e);
            } finally {
                if (client != null) {
                    jedisPools.get(pos).returnResource(client);
                }
            }
        }
        return true;
    }

    public List<String> mget(String[] keys) {
        int length = keys.length;
        int[] poss = new int[length];
        Map<Integer, List<String>> multiKey = new HashMap<Integer, List<String>>();
        for (int i = 0; i < length; ++i) {
            String key = keys[i];
            int pos = getHostPos(key);
            poss[i] = pos;
            if (multiKey.containsKey(pos)) {
                multiKey.get(pos).add(key);
            } else {
                List<String> kv = new ArrayList<String>();
                kv.add(key);
                multiKey.put(pos, kv);
            }
        }

        Map<Integer, List<String>> multiValue = new HashMap<Integer, List<String>>();
        List<String> resValue = new ArrayList<String>();
        for (Map.Entry<Integer, List<String>> entry : multiKey.entrySet()) {
            int pos = entry.getKey();
            Jedis client = null;
            try {
                client = jedisPools.get(pos).getResource();
                List<String> ls = entry.getValue();
                List<String> singleRes = client.mget(ls.toArray(new String[ls.size()]));
                multiValue.put(pos, singleRes);
            } catch (Exception e) {
                if (client != null) {
                    client.disconnect();
                }
                logger.error("Redis is error", e);
            } finally {
                if (client != null) {
                    jedisPools.get(pos).returnResource(client);
                }
            }
        }
        Map<Integer, Integer> indexs = new HashMap<Integer, Integer>();
        for (int pos : multiValue.keySet()) {
            indexs.put(pos, 0);
        }
        for (int i = 0; i < length; ++i) {
            Integer j = indexs.get(poss[i]);
            List<String> list = multiValue.get(poss[i]);
            resValue.add(list.get(j));
            indexs.put(poss[i], ++j);
        }
        return resValue;
    }

    public Long del(String[] keys) {
        int length = keys.length;
        int[] poss = new int[length];
        Map<Integer, List<String>> multiKey = new HashMap<Integer, List<String>>();
        for (int i = 0; i < length; ++i) {
            String key = keys[i];
            int pos = getHostPos(key);
            poss[i] = pos;
            if (multiKey.containsKey(pos)) {
                multiKey.get(pos).add(key);
            } else {
                List<String> kv = new ArrayList<String>();
                kv.add(key);
                multiKey.put(pos, kv);
            }
        }
        Long res = 0L;
        for (Map.Entry<Integer, List<String>> entry : multiKey.entrySet()) {
            int pos = entry.getKey();
            Jedis client = null;
            try {
                client = jedisPools.get(pos).getResource();
                List<String> ls = entry.getValue();
                res += client.del(ls.toArray(new String[ls.size()]));
            } catch (Exception e) {
                if (client != null) {
                    client.disconnect();
                }
                logger.error("Redis is error", e);
            } finally {
                if (client != null) {
                    jedisPools.get(pos).returnResource(client);
                }
            }
        }
        return res;
    }

    /*
     * 批量设置过期时间 params: string[]
     */
    public boolean mexpire(String[] keyexpires) {
        int length = keyexpires.length;
        if (length % 2 != 0) {
            return false;
        }
        Map<Integer, List<String>> multiKV = new HashMap<Integer, List<String>>();
        for (int i = 0; i < length; i += 2) {
            String key = keyexpires[i];
            String expireTime = keyexpires[i + 1];
            int pos = getHostPos(key);
            // System.out.println(pos);
            if (multiKV.containsKey(pos)) {
                multiKV.get(pos).add(key);
                multiKV.get(pos).add(expireTime);
            } else {
                List<String> kv = new ArrayList<String>();
                kv.add(key);
                kv.add(expireTime);
                multiKV.put(pos, kv);
            }
        }

        for (Map.Entry<Integer, List<String>> entry : multiKV.entrySet()) {
            int pos = entry.getKey();
            Jedis client = null;
            try {
                client = jedisPools.get(pos).getResource();
                Pipeline p = client.pipelined();
                List<String> ls = entry.getValue();
                for (Iterator<String> iter = ls.iterator(); iter.hasNext(); ) {
                    p.expire(iter.next(), Integer.parseInt(iter.next()));
                }
                p.sync();
            } catch (Exception e) {
                if (client != null) {
                    client.disconnect();
                }
                logger.error("Redis is error", e);
            } finally {
                if (client != null) {
                    jedisPools.get(pos).returnResource(client);
                }
            }
        }
        return true;
    }

    /**
     * Add the specified member to the set value stored at key. If member is already a member of the set no operation is
     * performed. If key does not exist a new set with the specified member as sole member is created. If the key exists
     * but does not hold a set value an error is returned. <p> Time complexity O(1)
     *
     * @return Integer reply, specifically: 1 if the new element was added 0 if the element was already a member of the
     * set
     */
    public Long sadd(final String key, final String... members) {
        JedisPool jedisPool = getJedisPool(key);
        Jedis client = null;
        try {
            client = jedisPool.getResource();
            return client.sadd(key, members);
        } catch (Exception e) {
            if (client != null) {
                client.disconnect();
            }
            logger.error("key=" + key);
            logger.error("getFromRedis is error", e);
            return null;
        } finally {
            if (client != null) {
                jedisPool.returnResource(client);
            }
        }
    }

    /**
     * Return 1 if member is a member of the set stored at key, otherwise 0 is returned. <p> Time complexity O(1)
     *
     * @return Integer reply, specifically: 1 if the element is a member of the set 0 if the element is not a member of
     * the set OR if the key does not exist
     */
    public boolean sismember(final String key, final String member) {
        JedisPool jedisPool = getJedisPool(key);
        Jedis client = null;
        try {
            client = jedisPool.getResource();
            return client.sismember(key, member);
        } catch (Exception e) {
            if (client != null) {
                client.disconnect();
            }
            logger.error("key=" + key);
            logger.error("getFromRedis is error", e);
            return false;
        } finally {
            if (client != null) {
                jedisPool.returnResource(client);
            }
        }
    }

    /**
     * Return all the members (elements) of the set value stored at key. This is just syntax glue for {@link #(String...)
     * SINTER}. <p> Time complexity O(N)
     *
     * @return Multi bulk reply
     */
    public Set<String> smembers(final String key) {
        JedisPool jedisPool = getJedisPool(key);
        Jedis client = null;
        try {
            client = jedisPool.getResource();
            return client.smembers(key);
        } catch (Exception e) {
            if (client != null) {
                client.disconnect();
            }
            logger.error("key=" + key);
            logger.error("getFromRedis is error", e);
            return null;
        } finally {
            if (client != null) {
                jedisPool.returnResource(client);
            }
        }
    }

    /**
     * Return the set cardinality (number of elements). If the key does not exist 0 is returned, like for empty sets.
     *
     * @return Integer reply, specifically: the cardinality (number of elements) of the set as an integer.
     */
    public Long scard(final String key) {
        JedisPool jedisPool = getJedisPool(key);
        Jedis client = null;
        try {
            client = jedisPool.getResource();
            return client.scard(key);
        } catch (Exception e) {
            if (client != null) {
                client.disconnect();
            }
            logger.error("key=" + key);
            logger.error("getFromRedis is error", e);
            return null;
        } finally {
            if (client != null) {
                jedisPool.returnResource(client);
            }
        }
    }

    // 获得key对应的存储节点的number
    private int getHostPos(String key) {
        long hashCode = murmurHash.hash(key) & 0x00000000ffffffffL;
        long intKey = hashCode % 65536;
        return _map.get((int) intKey);
    }

    private JedisPool getJedisPool(String key) {
        long hashCode = (murmurHash.hash(key)) & 0x00000000ffffffffL;
        long intKey = hashCode % 65536;
        return jedisPools.get(_map.get((int) intKey));
    }

    /*
     * the Strategy means The ratio of cache storage will be 2:1 ,such as: 0 0 0
     * 1 1 1 2 2 0 3 0 0 4 1 1 5 2 0 6 0 0 7 1 1 8 2 0 9 0 0
     */
    private int
    convertStrategy(int i) {
        return i % 3 % 2;
    }

    // you can change _map's some value based on your machines ability
    private boolean set_map() {
        for (int i = 0; i < 65536; ++i) {
            this._map.put(i, convertStrategy(i));
        }
        return true;
    }

    private boolean isPrefix(String s1, String s2) {
        int len1 = s1.length();
        int len2 = s2.length();
        int minLen = len1 < len2 ? len1 : len2;
        for (int i = 0; i < minLen; i++) {
            if (s1.charAt(i) != s2.charAt(i)) {
                return false;
            }
        }
        return true;
    }
    
    /*
     * 获取单个key对应的value 批量操作请尽量使用mget
     */
    public Long expire(String key, int expiredSeconds) {
        JedisPool jedisPool = getJedisPool(key);
        Jedis client = null;
        try {
            client = jedisPool.getResource();
            return client.expire(key, expiredSeconds);
        } catch (Exception e) {
            if (client != null) {
                client.disconnect();
            }
            logger.error("key=" + key);
            logger.error("getFromRedis is error", e);
            return null;
        } finally {
            if (client != null) {
                jedisPool.returnResource(client);
            }
        }
    }
    
    /*
     * 加锁
     */
    public Long setnx(String key, String value) {
        JedisPool jedisPool = getJedisPool(key);
        Jedis client = null;
        try {
            client = jedisPool.getResource();
            return client.setnx(key, value);
        } catch (Exception e) {
            if (client != null) {
                client.disconnect();
            }
            logger.error("key=" + key);
            logger.error("getFromRedis is error", e);
            return null;
        } finally {
            if (client != null) {
                jedisPool.returnResource(client);
            }
        }
    }
}
